/*
 * See Licensing and Copyright notice in naev.h
 */
/**
 * @file faction.c
 *
 * @brief Handles the Naev factions.
 */
/** @cond */
#include <assert.h>
#include <stdlib.h>

#include <SDL3/SDL_timer.h>

#include "naev.h"
/** @endcond */

#include "faction.h"

#include "array.h"
#include "colour.h"
#include "hook.h"
#include "log.h"
#include "ndata.h"
#include "nlua.h"
#include "nlua_system.h"
#include "nxml.h"
#include "opengl_tex.h"
#include "player.h"
#include "space.h"

#define XML_FACTION_ID "Factions" /**< XML section identifier */
#define XML_FACTION_TAG "faction" /**< XML tag identifier. */

#define FACTION_STATIC                                                         \
   ( 1 << 0 ) /**< Faction doesn't change standing with player. */
#define FACTION_INVISIBLE                                                      \
   ( 1 << 1 )                      /**< Faction isn't exposed to the player. */
#define FACTION_KNOWN ( 1 << 2 )   /**< Faction is known to the player. */
#define FACTION_DYNAMIC ( 1 << 3 ) /**< Faction was created dynamically. */
#define FACTION_USESHIDDENJUMPS                                                \
   ( 1 << 4 ) /**< Faction will try to use hidden jumps when possible. */
#define FACTION_REPOVERRIDE ( 1 << 5 ) /* Reputation is being overidden. */

#define faction_setFlag( fa, f ) ( ( fa )->flags |= ( f ) )
#define faction_rmFlag( fa, f ) ( ( fa )->flags &= ~( f ) )
#define faction_isFlag( fa, f ) ( ( fa )->flags & ( f ) )
#define faction_isKnown_( fa ) ( ( fa )->flags & ( FACTION_KNOWN ) )

typedef enum FactionGrid {
   GRID_NONE = 0,
   GRID_ENEMIES,
   GRID_ALLIES,
   GRID_NEUTRAL,
} FactionGrid;

int faction_player; /**< Player faction identifier. */

/**
 * @struct Faction
 *
 * @brief Represents a faction.
 */
typedef struct Faction_ {
   char  *name;        /**< Normal Name. */
   char  *longname;    /**< Long Name. */
   char  *displayname; /**< Display name. */
   char  *mapname;     /**< Name to use on the map. */
   char  *ai;          /**< Name of the faction's default pilot AI. */
   char  *description; /**< Description of the faction. */
   double local_th;    /**< Local threshold between adjacent systems. */

   /* Scripts. */
   char *script_standing;
   char *script_spawn;
   char *script_equip;

   /* Graphics. */
   glTexture *logo;   /**< Tiny logo. */
   glColour   colour; /**< Faction specific colour. */

   /* Enemies */
   int *enemies; /**< Enemies by ID of the faction. */

   /* Allies */
   int *allies; /**< Allies by ID of the faction. */

   /* True neutrals. */
   int *neutrals; /**< Allies by ID of the faction. */

   /* Player information. */
   double player_def; /**< Default player standing. */
   double player;     /**< Standing with player - from -100 to 100 */
   double override;   /**< Override the player standing. */

   /* Scheduler. */
   nlua_env *sched_env; /**< Lua scheduler script. */

   /* Behaviour. */
   double    friendly_at;        /**< Value of "standing.friendly_at" */
   nlua_env *lua_env;            /**< Faction specific environment. */
   int       lua_hit;            /**< "standing.hit" */
   int       lua_hit_test;       /**< "standing.hit_test" */
   int       lua_text_rank;      /**< "standing.text_rank" */
   int       lua_text_broad;     /**< "standing.text_broad" */
   int       lua_reputation_max; /**< "standing.reputation_max" */

   /* Safe lanes. */
   double
      lane_length_per_presence; /**< Influences the choice to build patrolled
                                   safe lanes in the way the name suggests. */
   double lane_base_cost;       /**< Base cost of the lane. */

   /* Presence stuff. */
   FactionGenerator
      *generators; /**< Secondary factions generated by this faction. */

   /* Equipping. */
   nlua_env *equip_env; /**< Faction equipper enviornment. */

   /* Flags. */
   unsigned int flags;  /**< Flags affecting the faction. */
   unsigned int oflags; /**< Original flags (for when new game is started). */

   /* Tags. */
   char **tags; /**< array.h: List of tags the faction has. */
} Faction;

static Faction *faction_stack = NULL; /**< Faction stack. */
static int     *faction_grid  = NULL; /**< Grid of faction status. */
static size_t   faction_mgrid = 0;    /**< Allocated memory. */

/*
 * Prototypes
 */
/* static */
static int    faction_getRaw( const char *name );
static void   faction_freeOne( Faction *f );
static void   faction_sanitizePlayer( Faction *faction );
static double faction_hitLua( int f, const StarSystem *sys, double mod,
                              const char *source, int secondary,
                              int primary_faction );
static int    faction_parse( Faction *temp, const char *file );
static int    faction_parseSocial( const char *file );
static void faction_addStandingScript( Faction *temp, const char *scriptname );
static void faction_computeGrid( void );
/* externed */
int pfaction_save( xmlTextWriterPtr writer );
int pfaction_load( xmlNodePtr parent );

static int faction_cmp( const void *p1, const void *p2 )
{
   const Faction *f1 = p1;
   const Faction *f2 = p2;
   return strcmp( f1->name, f2->name );
}

/**
 * @brief Gets a faction ID by name.
 *
 *    @param name Name of the faction to seek.
 *    @return ID of the faction.
 */
static int faction_getRaw( const char *name )
{
   if ( name == NULL )
      return -1;

   /* Escorts are part of the "player" faction. */
   if ( strcmp( name, "Escort" ) == 0 )
      return FACTION_PLAYER;

   if ( name != NULL ) {
      int i;
      for ( i = 0; i < array_size( faction_stack ); i++ )
         if ( strcmp( faction_stack[i].name, name ) == 0 )
            break;

      if ( i != array_size( faction_stack ) )
         return i;

      /* Dynamic factions are why we can't have nice things.
      const Faction f = { .name = (char*)name };
      Faction *found = bsearch( &f, faction_stack, array_size(faction_stack),
      sizeof(Faction), faction_cmp ); if (found != NULL) return found -
      faction_stack;
      */
   }
   return -1;
}

/**
 * @brief Checks to see if a faction exists by name.
 *
 *    @param name Name of the faction to seek.
 *    @return ID of the faction.
 */
int faction_exists( const char *name )
{
   return faction_getRaw( name ) != -1;
}

/**
 * @brief Gets a faction ID by name.
 *
 *    @param name Name of the faction to seek.
 *    @return ID of the faction.
 */
int faction_get( const char *name )
{
   int id = faction_getRaw( name );
   if ( id < 0 )
      WARN( _( "Faction '%s' not found in stack." ), name );
   return id;
}

/**
 * @brief Returns all faction IDs in an array (array.h).
 */
int *faction_getAll( void )
{
   int *f = array_create_size( int, array_size( faction_stack ) );
   for ( int i = 0; i < array_size( faction_stack ); i++ )
      array_push_back( &f, i );
   return f;
}

/**
 * @brief Returns all non-invisible faction IDs in an array (array.h).
 */
int *faction_getAllVisible( void )
{
   int *f = array_create_size( int, array_size( faction_stack ) );
   for ( int i = 0; i < array_size( faction_stack ); i++ )
      if ( !faction_isFlag( &faction_stack[i], FACTION_INVISIBLE ) )
         array_push_back( &f, i );
   return f;
}

/**
 * @brief Gets all the known factions in an array (array.h).
 */
int *faction_getKnown()
{
   int *f = array_create_size( int, array_size( faction_stack ) );
   /* Get IDs. */
   for ( int i = 0; i < array_size( faction_stack ); i++ )
      if ( !faction_isFlag( &faction_stack[i], FACTION_INVISIBLE ) &&
           faction_isKnown_( &faction_stack[i] ) )
         array_push_back( &f, i );
   return f;
}

/**
 * @brief Clears the known factions.
 */
void faction_clearKnown()
{
   for ( int i = 0; i < array_size( faction_stack ); i++ )
      if ( faction_isKnown_( &faction_stack[i] ) )
         faction_rmFlag( &faction_stack[i], FACTION_KNOWN );
}

/**
 * @brief Is the faction static?
 */
int faction_isStatic( int id )
{
   return faction_isFlag( &faction_stack[id], FACTION_STATIC );
}

/**
 * @brief Is the faction invisible?
 */
int faction_isInvisible( int id )
{
   return faction_isFlag( &faction_stack[id], FACTION_INVISIBLE );
}

/**
 * @brief Sets the faction's invisible state
 */
int faction_setInvisible( int id, int state )
{
   if ( !faction_isFaction( id ) ) {
      WARN( _( "Faction id '%d' is invalid." ), id );
      return -1;
   }
   if ( state )
      faction_setFlag( &faction_stack[id], FACTION_INVISIBLE );
   else
      faction_rmFlag( &faction_stack[id], FACTION_INVISIBLE );

   return 0;
}

/**
 * @brief Is the faction known?
 */
int faction_isKnown( int id )
{
   return faction_isKnown_( &faction_stack[id] );
}

/**
 * @brief Is faction dynamic.
 */
int faction_isDynamic( int id )
{
   return faction_isFlag( &faction_stack[id], FACTION_DYNAMIC );
}

/**
 * @brief Sets the factions known state
 */
int faction_setKnown( int id, int state )
{
   if ( state )
      faction_setFlag( &faction_stack[id], FACTION_KNOWN );
   else
      faction_rmFlag( &faction_stack[id], FACTION_KNOWN );
   return 0;
}

/**
 * @brief Gets a factions "real" (internal) name.
 *
 *    @param f Faction to get the name of.
 *    @return Name of the faction (internal/English).
 */
const char *faction_name( int f )
{
   if ( !faction_isFaction( f ) ) {
      WARN( _( "Faction id '%d' is invalid." ), f );
      return NULL;
   }
   /* Don't want player to see their escorts as "Player" faction. */
   if ( f == FACTION_PLAYER )
      return "Escort";

   return faction_stack[f].name;
}

/**
 * @brief Gets a factions short name (human-readable).
 *
 *    @param f Faction to get the name of.
 *    @return Name of the faction (in player's native language).
 */
const char *faction_shortname( int f )
{
   if ( !faction_isFaction( f ) ) {
      WARN( _( "Faction id '%d' is invalid." ), f );
      return NULL;
   }
   /* Don't want player to see their escorts as "Player" faction. */
   if ( f == FACTION_PLAYER )
      return _( "Escort" );

   /* Possibly get display name. */
   if ( faction_stack[f].displayname != NULL )
      return _( faction_stack[f].displayname );

   return _( faction_stack[f].name );
}

/**
 * @brief Gets the faction's long name (formal, human-readable).
 *
 *    @param f Faction to get the name of.
 *    @return The faction's long name (in player's native language).
 */
const char *faction_longname( int f )
{
   if ( !faction_isFaction( f ) ) {
      WARN( _( "Faction id '%d' is invalid." ), f );
      return NULL;
   }
   if ( faction_stack[f].longname != NULL )
      return _( faction_stack[f].longname );
   return _( faction_stack[f].name );
}

/**
 * @brief Gets the faction's map name (translated).
 *
 *    @param f Faction to get the name of.
 *    @return The faction's map name (in User's language).
 */
const char *faction_mapname( int f )
{
   if ( !faction_isFaction( f ) ) {
      WARN( _( "Faction id '%d' is invalid." ), f );
      return NULL;
   }
   if ( faction_stack[f].mapname != NULL )
      return _( faction_stack[f].mapname );
   return _( faction_stack[f].name );
}

/**
 * @brief Gets the faction's description (translated).
 *
 *    @param f Faction to get the name of.
 *    @return The faction's description (in User's language).
 */
const char *faction_description( int f )
{
   if ( !faction_isFaction( f ) ) {
      WARN( _( "Faction id '%d' is invalid." ), f );
      return NULL;
   }
   if ( faction_stack[f].description != NULL )
      return _( faction_stack[f].description );
   return NULL;
}

/**
 * @brief Gets the name of the default AI profile for the faction's pilots.
 *
 *    @param f Faction ID.
 *    @return The faction's AI profile name.
 */
const char *faction_default_ai( int f )
{
   if ( !faction_isFaction( f ) ) {
      WARN( _( "Faction id '%d' is invalid." ), f );
      return NULL;
   }
   return faction_stack[f].ai;
}

/**
 * @brief Gets the tags the faction has.
 *
 *    @param f Faction ID.
 *    @return The tags the faction has (array.h).
 */
const char *const *faction_tags( int f )
{
   if ( !faction_isFaction( f ) ) {
      WARN( _( "Faction id '%d' is invalid." ), f );
      return NULL;
   }
   return (const char **)faction_stack[f].tags;
}

/**
 * @brief Gets the faction's weight for patrolled safe-lane construction (0
 * means they don't build lanes).
 */
double faction_lane_length_per_presence( int f )
{
   if ( !faction_isFaction( f ) ) {
      WARN( _( "Faction id '%d' is invalid." ), f );
      return 0.;
   }
   return faction_stack[f].lane_length_per_presence;
}

/**
 * @brief Gets the faction's weight for patrolled safe-lane construction;
 */
double faction_lane_base_cost( int f )
{
   if ( !faction_isFaction( f ) ) {
      WARN( _( "Faction id '%d' is invalid." ), f );
      return 0.;
   }
   return faction_stack[f].lane_base_cost;
}

/**
 * @brief Gets the faction's logo (ideally 256x256).
 *
 *    @param f Faction to get the logo of.
 *    @return The faction's logo image.
 */
const glTexture *faction_logo( int f )
{
   if ( !faction_isFaction( f ) ) {
      WARN( _( "Faction id '%d' is invalid." ), f );
      return NULL;
   }
   return faction_stack[f].logo;
}

/**
 * @brief Gets the colour of the faction
 *
 *    @param f Faction to get the colour of.
 *    @return The faction's colour
 */
const glColour *faction_colour( int f )
{
   if ( !faction_isFaction( f ) ) {
      WARN( _( "Faction id '%d' is invalid." ), f );
      return NULL;
   }
   return &faction_stack[f].colour;
}

/**
 * @brief Gets the list of enemies of a faction.
 *
 *    @param f Faction to get enemies of.
 *    @return Array (array.h): The enemies of the faction.
 */
const int *faction_getEnemies( int f )
{
   if ( !faction_isFaction( f ) ) {
      WARN( _( "Faction id '%d' is invalid." ), f );
      return NULL;
   }

   /* Player's faction ratings can change, so regenerate each call. */
   if ( f == FACTION_PLAYER ) {
      int *enemies = array_create( int );

      for ( int i = 0; i < array_size( faction_stack ); i++ )
         if ( faction_isPlayerEnemy( i ) ) {
            int *tmp = &array_grow( &enemies );
            *tmp     = i;
         }

      array_free( faction_stack[f].enemies );
      faction_stack[f].enemies = enemies;
   }

   return faction_stack[f].enemies;
}

/**
 * @brief Gets the list of allies of a faction.
 *
 *    @param f Faction to get allies of.
 *    @return Array (array.h): The allies of the faction.
 */
const int *faction_getAllies( int f )
{
   if ( !faction_isFaction( f ) ) {
      WARN( _( "Faction id '%d' is invalid." ), f );
      return NULL;
   }

   /* Player's faction ratings can change, so regenerate each call. */
   if ( f == FACTION_PLAYER ) {
      int *allies = array_create( int );

      for ( int i = 0; i < array_size( faction_stack ); i++ )
         if ( faction_isPlayerFriend( i ) ) {
            int *tmp = &array_grow( &allies );
            *tmp     = i;
         }

      array_free( faction_stack[f].allies );
      faction_stack[f].allies = allies;
   }

   return faction_stack[f].allies;
}

/**
 * @brief Clears all the enemies of a dynamic faction.
 *
 *    @param f Faction to clear enemies of.
 */
void faction_clearEnemy( int f )
{
   Faction *ff;
   /* Get faction. */
   if ( faction_isFaction( f ) )
      ff = &faction_stack[f];
   else { /* f is invalid */
      WARN( _( "Faction id '%d' is invalid." ), f );
      return;
   }
   array_erase( &ff->enemies, array_begin( ff->enemies ),
                array_end( ff->enemies ) );
   faction_computeGrid();
}

/**
 * @brief Adds an enemy to the faction's enemies list.
 *
 *    @param f The faction to add an enemy to.
 *    @param o The other faction to make an enemy.
 */
void faction_addEnemy( int f, int o )
{
   Faction *ff;
   int     *tmp;

   if ( f == o )
      return;

   if ( faction_isFaction( f ) )
      ff = &faction_stack[f];
   else { /* f is invalid */
      WARN( _( "Faction id '%d' is invalid." ), f );
      return;
   }

   if ( !faction_isFaction( o ) ) { /* o is invalid */
      WARN( _( "Faction id '%d' is invalid." ), o );
      return;
   }

   /* Player cannot be made an enemy this way */
   if ( f == FACTION_PLAYER ) {
      WARN( _( "%d is the player faction" ), f );
      return;
   }
   if ( o == FACTION_PLAYER ) {
      WARN( _( "%d is the player faction" ), o );
      return;
   }

   for ( int i = 0; i < array_size( ff->enemies ); i++ ) {
      if ( ff->enemies[i] == o )
         return;
   }

   tmp  = &array_grow( &ff->enemies );
   *tmp = o;

   faction_computeGrid();
}

/**
 * @brief Removes an enemy from the faction's enemies list.
 *
 *    @param f The faction to remove an enemy from.
 *    @param o The other faction to remove as an enemy.
 */
void faction_rmEnemy( int f, int o )
{
   Faction *ff;

   if ( f == o )
      return;

   if ( faction_isFaction( f ) )
      ff = &faction_stack[f];
   else { /* f is invalid */
      WARN( _( "Faction id '%d' is invalid." ), f );
      return;
   }

   for ( int i = 0; i < array_size( ff->enemies ); i++ ) {
      if ( ff->enemies[i] == o ) {
         array_erase( &ff->enemies, &ff->enemies[i], &ff->enemies[i + 1] );
         faction_computeGrid();
         return;
      }
   }
}

/**
 * @brief Clears all the ally of a dynamic faction.
 *
 *    @param f Faction to clear ally of.
 */
void faction_clearAlly( int f )
{
   Faction *ff;
   /* Get faction. */
   if ( faction_isFaction( f ) )
      ff = &faction_stack[f];
   else { /* f is invalid */
      WARN( _( "Faction id '%d' is invalid." ), f );
      return;
   }
   array_erase( &ff->allies, array_begin( ff->allies ),
                array_end( ff->allies ) );
   faction_computeGrid();
}

/**
 * @brief Adds an ally to the faction's allies list.
 *
 *    @param f The faction to add an ally to.
 *    @param o The other faction to make an ally.
 */
void faction_addAlly( int f, int o )
{
   Faction *ff;
   int     *tmp;

   if ( f == o )
      return;

   if ( faction_isFaction( f ) )
      ff = &faction_stack[f];
   else { /* f is invalid */
      WARN( _( "Faction id '%d' is invalid." ), f );
      return;
   }

   if ( !faction_isFaction( o ) ) { /* o is invalid */
      WARN( _( "Faction id '%d' is invalid." ), o );
      return;
   }

   /* player cannot be made an ally this way */
   if ( f == FACTION_PLAYER ) {
      WARN( _( "%d is the player faction" ), f );
      return;
   }
   if ( o == FACTION_PLAYER ) {
      WARN( _( "%d is the player faction" ), o );
      return;
   }

   for ( int i = 0; i < array_size( ff->allies ); i++ ) {
      if ( ff->allies[i] == o )
         return;
   }

   tmp  = &array_grow( &ff->allies );
   *tmp = o;

   faction_computeGrid();
}

/**
 * @brief Removes an ally from the faction's allies list.
 *
 *    @param f The faction to remove an ally from.
 *    @param o The other faction to remove as an ally.
 */
void faction_rmAlly( int f, int o )
{
   Faction *ff;

   if ( f == o )
      return;

   if ( faction_isFaction( f ) )
      ff = &faction_stack[f];
   else { /* f is invalid */
      WARN( _( "Faction id '%d' is invalid." ), f );
      return;
   }

   for ( int i = 0; i < array_size( ff->allies ); i++ ) {
      if ( ff->allies[i] == o ) {
         array_erase( &ff->allies, &ff->allies[i], &ff->allies[i + 1] );
         faction_computeGrid();
         return;
      }
   }
}

/**
 * @brief Adds an neutral to the faction's neutrals list.
 *
 *    @param f The faction to add an neutral to.
 *    @param o The other faction to make an neutral.
 */
void faction_addNeutral( int f, int o )
{
   Faction *ff;
   int     *tmp;

   if ( f == o )
      return;

   if ( faction_isFaction( f ) )
      ff = &faction_stack[f];
   else { /* f is invalid */
      WARN( _( "Faction id '%d' is invalid." ), f );
      return;
   }

   if ( !faction_isFaction( o ) ) { /* o is invalid */
      WARN( _( "Faction id '%d' is invalid." ), o );
      return;
   }

   /* player cannot be made an neutral this way */
   if ( f == FACTION_PLAYER ) {
      WARN( _( "%d is the player faction" ), f );
      return;
   }
   if ( o == FACTION_PLAYER ) {
      WARN( _( "%d is the player faction" ), o );
      return;
   }

   for ( int i = 0; i < array_size( ff->neutrals ); i++ ) {
      if ( ff->neutrals[i] == o )
         return;
   }

   tmp  = &array_grow( &ff->neutrals );
   *tmp = o;

   faction_computeGrid();
}

/**
 * @brief Removes an neutral from the faction's neutrals list.
 *
 *    @param f The faction to remove an neutral from.
 *    @param o The other faction to remove as an neutral.
 */
void faction_rmNeutral( int f, int o )
{
   Faction *ff;

   if ( f == o )
      return;

   if ( faction_isFaction( f ) )
      ff = &faction_stack[f];
   else { /* f is invalid */
      WARN( _( "Faction id '%d' is invalid." ), f );
      return;
   }

   for ( int i = 0; i < array_size( ff->neutrals ); i++ ) {
      if ( ff->neutrals[i] == o ) {
         array_erase( &ff->neutrals, &ff->neutrals[i], &ff->neutrals[i + 1] );
         faction_computeGrid();
         return;
      }
   }
}

/**
 * @brief Gets the state associated to the faction scheduler.
 */
nlua_env *faction_getScheduler( int f )
{
   if ( !faction_isFaction( f ) ) {
      WARN( _( "Faction id '%d' is invalid." ), f );
      return NULL;
   }
   return faction_stack[f].sched_env;
}

/**
 * @brief Gets the equipper state associated to the faction scheduler.
 */
nlua_env *faction_getEquipper( int f )
{
   if ( !faction_isFaction( f ) ) {
      WARN( _( "Faction id '%d' is invalid." ), f );
      return NULL;
   }
   return faction_stack[f].equip_env;
}

/**
 * @brief Sanitizes player faction standing.
 *
 *    @param faction Faction to sanitize.
 */
static void faction_sanitizePlayer( Faction *faction )
{
   faction->player = CLAMP( -100., 100., faction->player );
}

/**
 * @brief Mods player using the power of Lua.
 */
static double faction_hitLua( int f, const StarSystem *sys, double mod,
                              const char *source, int secondary,
                              int primary_faction )
{
   /* Ignore it if player is dead. */
   if ( player.p == NULL )
      return 0.;

   Faction *faction = &faction_stack[f];

   /* Make sure it's not static. */
   if ( faction_isFlag( faction, FACTION_STATIC ) )
      return 0.;

   /* Overriden, so doesn't budge. */
   if ( faction_isFlag( faction, FACTION_REPOVERRIDE ) )
      return 0.;

   /* Set up the function:
    * standing:hit( sys, amount, source, secondary ) */
   lua_rawgeti( naevL, LUA_REGISTRYINDEX, faction->lua_hit );
   if ( sys != NULL )
      lua_pushsystem( naevL, sys->id );
   else
      lua_pushnil( naevL );
   lua_pushnumber( naevL, mod );
   lua_pushstring( naevL, source );
   lua_pushinteger( naevL, secondary );
   if ( faction_isFaction( primary_faction ) )
      lua_pushfaction( naevL, primary_faction );
   else
      lua_pushnil( naevL );

   /* Call function. */
   if ( nlua_pcall( faction->lua_env, 5, 1 ) ) { /* An error occurred. */
      WARN( _( "Faction '%s': %s" ), faction->name, lua_tostring( naevL, -1 ) );
      lua_pop( naevL, 1 );
      return 0.;
   }

   /* Sanitize just in case. */
   faction_sanitizePlayer( faction );

   /* Run hook if necessary. */
   double delta = lua_tonumber( naevL, -1 );
   lua_pop( naevL, 1 );
   if ( FABS( delta ) > DOUBLE_TOL ) {
      HookParam hparam[7];
      hparam[0].type = HOOK_PARAM_FACTION;
      hparam[0].u.lf = f;
      if ( sys != NULL ) {
         hparam[1].type = HOOK_PARAM_SSYS;
         hparam[1].u.ls = sys->id;
      } else
         hparam[1].type = HOOK_PARAM_NIL;
      hparam[2].type  = HOOK_PARAM_NUMBER;
      hparam[2].u.num = delta;
      hparam[3].type  = HOOK_PARAM_STRING;
      hparam[3].u.str = source;
      hparam[4].type  = HOOK_PARAM_NUMBER;
      hparam[4].u.num = secondary;
      if ( faction_isFaction( primary_faction ) ) {
         hparam[5].type = HOOK_PARAM_FACTION;
         hparam[5].u.lf = primary_faction;
      } else
         hparam[5].type = HOOK_PARAM_NIL;
      hparam[6].type = HOOK_PARAM_SENTINEL;
      hooks_runParam( "standing", hparam );

      /* Tell space the faction changed. */
      space_factionChange();
   }

   return delta;
}

/**
 * @brief Handles a faction hit against a faction and how to apply it.
 */
double faction_hit( int f, const StarSystem *sys, double mod,
                    const char *source, int single )
{
   double ret;
   if ( !faction_isFaction( f ) ) {
      WARN( _( "Faction id '%d' is invalid." ), f );
      return 0;
   }
   const Faction *faction = &faction_stack[f];

   /* Modify faction standing with parent faction. */
   ret = faction_hitLua( f, sys, mod, source, 0, -1 );

   /* Don't apply secondary hits. */
   if ( !single ) {
      /* Now mod allies to a lesser degree */
      for ( int i = 0; i < array_size( faction->allies ); i++ )
         /* Modify faction standing */
         faction_hitLua( faction->allies[i], sys, mod, source, 1, f );
      /* Now mod enemies */
      for ( int i = 0; i < array_size( faction->enemies ); i++ )
         /* Modify faction standing. */
         faction_hitLua( faction->enemies[i], sys, -mod, source, -1, f );
   }

   return ret;
}

/**
 * @brief Tests a faction hit to see how much it would apply. Does not actually
 * modify standing.
 */
double faction_hitTest( int f, const StarSystem *sys, double mod,
                        const char *source )
{
   if ( !faction_isFaction( f ) ) {
      WARN( _( "Faction id '%d' is invalid." ), f );
      return 0;
   }

   /* Ignore it if player is dead. */
   if ( player.p == NULL )
      return 0.;

   Faction *faction = &faction_stack[f];

   /* Make sure it's not static. */
   if ( faction_isFlag( faction, FACTION_STATIC ) )
      return 0.;

   /* Overriden, so doesn't budge. */
   if ( faction_isFlag( faction, FACTION_REPOVERRIDE ) )
      return 0.;

   /* Set up the function:
    * standing:hit( sys, amount, source, secondary ) */
   lua_rawgeti( naevL, LUA_REGISTRYINDEX, faction->lua_hit );
   if ( sys != NULL )
      lua_pushsystem( naevL, sys->id );
   else
      lua_pushnil( naevL );
   lua_pushnumber( naevL, mod );
   lua_pushstring( naevL, source );
   lua_pushinteger( naevL, 0 );
   lua_pushnil( naevL );

   /* Call function. */
   if ( nlua_pcall( faction->lua_env, 5, 1 ) ) { /* An error occurred. */
      WARN( _( "Faction '%s': %s" ), faction->name, lua_tostring( naevL, -1 ) );
      lua_pop( naevL, 1 );
      return 0.;
   }

   /* Sanitize just in case. */
   faction_sanitizePlayer( faction );

   /* Run hook if necessary. */
   double delta = lua_tonumber( naevL, -1 );
   lua_pop( naevL, 1 );
   return delta;
}

/**
 * @brief Modifies the player's standing with a faction.
 *
 * Affects enemies and allies too.
 *
 *    @param f Faction to modify player's standing.
 *    @param mod Modifier to modify by.
 *    @param source Source of the faction modifier.
 *
 *   Possible sources:
 *    - `destroy` : Pilot death.
 *    - `distress` : Pilot distress signal.
 *    - `script` : Either a mission or an event.
 */
void faction_modPlayer( int f, double mod, const char *source )
{
   Faction *faction;

   if ( !faction_isFaction( f ) ) {
      WARN( _( "Faction id '%d' is invalid." ), f );
      return;
   }
   faction = &faction_stack[f];

   /* Modify faction standing with parent faction. */
   faction_hitLua( f, NULL, mod, source, 0, -1 );

   /* Now mod allies to a lesser degree */
   for ( int i = 0; i < array_size( faction->allies ); i++ )
      /* Modify faction standing */
      faction_hitLua( faction->allies[i], NULL, mod, source, 1, f );

   /* Now mod enemies */
   for ( int i = 0; i < array_size( faction->enemies ); i++ )
      /* Modify faction standing. */
      faction_hitLua( faction->enemies[i], NULL, -mod, source, 1, f );
}

/**
 * @brief Modifies the player's standing without affecting others.
 *
 * Does not affect allies nor enemies.
 *
 *    @param f Faction whose standing to modify.
 *    @param mod Amount to modify standing by.
 *    @param source Source of the faction modifier.
 *
 *   Possible sources:
 *    - `destroy` : Pilot death.
 *    - `distress` : Pilot distress signal.
 *    - `script` : Either a mission or an event.
 *
 * @sa faction_modPlayer
 */
void faction_modPlayerSingle( int f, double mod, const char *source )
{
   if ( !faction_isFaction( f ) ) {
      WARN( _( "Faction id '%d' is invalid." ), f );
      return;
   }
   faction_hitLua( f, NULL, mod, source, 0, -1 );
}

/**
 * @brief Modifies the player's standing without affecting others.
 *
 * Does not affect allies nor enemies and does not run through the Lua script.
 *
 *    @param f Faction whose standing to modify.
 *    @param mod Amount to modify standing by.
 *
 * @sa faction_modPlayer
 */
void faction_modPlayerRaw( int f, double mod )
{
   Faction *faction;

   /* Ignore it if player is dead. */
   if ( player.p == NULL )
      return;

   if ( !faction_isFaction( f ) ) {
      WARN( _( "Faction id '%d' is invalid." ), f );
      return;
   }

   faction = &faction_stack[f];
   faction->player += mod;

   /* Deprecated so no hook now. */

   /* Sanitize just in case. */
   faction_sanitizePlayer( faction );

   /* Tell space the faction changed. */
   space_factionChange();
}

/**
 * @brief Sets the player's standing with a faction.
 *
 *    @param f Faction to set the player's standing for.
 *    @param value Value to set the player's standing to.
 */
void faction_setReputation( int f, double value )
{
   Faction *faction;
   double   mod;

   /* Ignore it if player is dead. */
   if ( player.p == NULL )
      return;

   if ( !faction_isFaction( f ) ) {
      WARN( _( "Faction id '%d' is invalid." ), f );
      return;
   }
   faction = &faction_stack[f];
   value   = CLAMP( -100., 100.,
                    value ); /* Gets reset by applying threshold otherwise. */

   /* In case of a dynamic faction, we just overwrite. */
   if ( faction_isFlag( faction, FACTION_DYNAMIC ) ) {
      faction->player = value;
      return;
   }

   /* Make sure it's not static. */
   if ( faction_isFlag( faction, FACTION_STATIC ) )
      return;

   /* Overriden, so doesn't budge. */
   if ( faction_isFlag( faction, FACTION_REPOVERRIDE ) )
      return;

   /* Global and hit. */
   mod             = value - faction->player;
   faction->player = value;

   /* Reset local. */
   StarSystem *sys_stack = system_getAll();
   for ( int j = 0; j < array_size( sys_stack ); j++ ) {
      StarSystem *sys = &sys_stack[j];
      for ( int k = 0; k < array_size( sys->presence ); k++ ) {
         SystemPresence *sp = &sys->presence[k];
         if ( sp->faction != f )
            continue;
         sp->local = value;
      }
   }

   /* Run hook if necessary. */
   HookParam hparam[7];
   hparam[0].type  = HOOK_PARAM_FACTION;
   hparam[0].u.lf  = f;
   hparam[1].type  = HOOK_PARAM_NIL;
   hparam[2].type  = HOOK_PARAM_NUMBER;
   hparam[2].u.num = mod;
   hparam[3].type  = HOOK_PARAM_STRING;
   hparam[3].u.str = "script";
   hparam[4].type  = HOOK_PARAM_NUMBER;
   hparam[4].u.num = 0;
   hparam[5].type  = HOOK_PARAM_NIL;
   hparam[6].type  = HOOK_PARAM_SENTINEL;
   hooks_runParam( "standing", hparam );

   /* Tell space the faction changed. */
   space_factionChange();
}

/**
 * @brief Gets the player's standing with a faction.
 *
 *    @param f Faction to get player's standing from.
 *    @return The standing the player has with the faction.
 */
double faction_reputation( int f )
{
   if ( faction_isFaction( f ) ) {
      const Faction *fac = &faction_stack[f];
      if ( faction_isFlag( fac, FACTION_REPOVERRIDE ) )
         return fac->override;
      else {
         return round( fac->player ) + 0.; // With IEC 60559 floating-point,
                                           // this should convert -0. to +0.
      }
   }
   WARN( _( "Faction id '%d' is invalid." ), f );
   return -1000.;
}

/**
 * @brief Gets the player's default standing with a faction.
 *
 *    @param f Faction to get player's default standing from.
 *    @return The default standing the player has with the faction.
 */
double faction_reputationDefault( int f )
{
   if ( faction_isFaction( f ) )
      return faction_stack[f].player_def;
   WARN( _( "Faction id '%d' is invalid." ), f );
   return -1000.;
}

/**
 * @brief Gets whether or not the player is a friend of the faction.
 *
 *    @param f Faction to check friendliness of.
 *    @return 1 if the player is a friend, 0 otherwise.
 */
int faction_isPlayerFriend( int f )
{
   const Faction *faction = &faction_stack[f];
   return ( faction_reputation( f ) >= faction->friendly_at );
}
int faction_isPlayerFriendSystem( int f, const StarSystem *sys )
{
   const Faction *faction = &faction_stack[f];
   return ( system_getReputationOrGlobal( sys, f ) >= faction->friendly_at );
}

/**
 * @brief Gets whether or not the player is an enemy of the faction.
 *
 *    @param f Faction to check hostility of.
 *    @return 1 if the player is an enemy, 0 otherwise.
 */
int faction_isPlayerEnemy( int f )
{
   return ( faction_reputation( f ) < 0. );
}
int faction_isPlayerEnemySystem( int f, const StarSystem *sys )
{
   return ( system_getReputationOrGlobal( sys, f ) < 0 );
}

/**
 * @brief Gets the colour of the faction based on it's standing with the player.
 *
 * Used to unify the colour checks all over.
 *
 *    @param f Faction to get the colour of based on player's standing.
 *    @return Pointer to the colour.
 */
const glColour *faction_reputationColour( int f )
{
   if ( f < 0 )
      return &cInert;
   else if ( areAllies( FACTION_PLAYER, f ) )
      return &cFriend;
   else if ( areEnemies( FACTION_PLAYER, f ) )
      return &cHostile;
   else
      return &cNeutral;
}
const glColour *faction_reputationColourSystem( int f, const StarSystem *sys )
{
   if ( f < 0 )
      return &cInert;
   else if ( areAlliesSystem( FACTION_PLAYER, f, sys ) )
      return &cFriend;
   else if ( areEnemiesSystem( FACTION_PLAYER, f, sys ) )
      return &cHostile;
   else
      return &cNeutral;
}

/**
 * @brief Gets the faction character associated to its standing with the player.
 *
 * Use this to do something like `#%c`, faction_reputationColourChar(
 * some_faction ) in the font print routines.
 *
 *    @param f Faction to get the colour of based on player's standing.
 *    @return The character associated to the faction.
 */
char faction_reputationColourChar( int f )
{
   if ( f < 0 )
      return 'I';
   else if ( areEnemies( FACTION_PLAYER, f ) )
      return 'H';
   else if ( areAllies( FACTION_PLAYER, f ) )
      return 'F';
   else
      return 'N';
}
char faction_reputationColourCharSystem( int f, const StarSystem *sys )
{
   if ( f < 0 )
      return 'I';
   else if ( areEnemiesSystem( FACTION_PLAYER, f, sys ) )
      return 'H';
   else if ( areAlliesSystem( FACTION_PLAYER, f, sys ) )
      return 'F';
   else
      return 'N';
}

/**
 * @brief Gets the player's standing in human readable form.
 *
 *    @param f Faction to get standing of.
 *    @return Human readable player's standing (in player's native language).
 */
const char *faction_getStandingText( int f )
{
   return faction_getStandingTextAtValue( f, faction_reputation( f ) );
}

/**
 * @brief Gets the player's standing in human readable form.
 *
 *    @param f Faction to get standing of.
 *    @param value Value to get the readable string from.
 *    @return Human readable player's standing (in player's native language).
 */
const char *faction_getStandingTextAtValue( int f, double value )
{
   Faction *faction;

   /* Ignore it if player is dead. */
   if ( player.p == NULL )
      return _( "???" );

   /* Escorts always have the same standing. */
   if ( f == FACTION_PLAYER )
      return _( "Escort" );

   faction = &faction_stack[f];

   if ( faction->lua_env == NULL )
      return _( "???" );
   else {
      const char *r;
      /* Set up the method:
       * standing:text_rank( standing ) */
      lua_rawgeti( naevL, LUA_REGISTRYINDEX, faction->lua_text_rank );
      lua_pushnumber( naevL, round( value ) );

      /* Call function. */
      if ( nlua_pcall( faction->lua_env, 1, 1 ) ) {
         /* An error occurred. */
         WARN( _( "Faction '%s': %s" ), faction->name,
               lua_tostring( naevL, -1 ) );
         lua_pop( naevL, 1 );
         return _( "???" );
      }

      /* Parse return. */
      if ( !lua_isstring( naevL, -1 ) ) {
         WARN(
            _( "Lua script for faction '%s' did not return a %s from '%s'." ),
            faction->name, _( "string" ), "text_rank" );
         r = _( "???" );
      } else
         r = lua_tostring( naevL, -1 ); /* Should be translated already. */
      lua_pop( naevL, 1 );
      return r;
   }
}

/**
 * @brief Gets the broad faction standing.
 *
 *    @param f Faction to get broad standing of.
 *    @param bribed Whether or not the respective pilot is bribed.
 *    @param override If positive sets to ally, if negative sets to hostile.
 *    @return Human readable broad player's standing.
 */
const char *faction_getStandingBroad( int f, int bribed, int override )
{
   Faction    *faction;
   const char *r;

   /* Ignore it if player is dead. */
   if ( player.p == NULL )
      return _( "???" );

   /* Escorts always have the same standing. */
   if ( f == FACTION_PLAYER )
      return _( "Escort" );

   faction = &faction_stack[f];

   if ( faction->lua_env == NULL )
      return _( "???" );

   /* Set up the method:
    * standing:text_broad( standing, bribed, override ) */
   lua_rawgeti( naevL, LUA_REGISTRYINDEX, faction->lua_text_broad );
   lua_pushnumber( naevL, ( faction_isFlag( faction, FACTION_REPOVERRIDE )
                               ? faction->override
                               : faction->player ) );
   lua_pushboolean( naevL, bribed );
   lua_pushinteger( naevL, override );

   /* Call function. */
   if ( nlua_pcall( faction->lua_env, 3, 1 ) ) {
      /* An error occurred. */
      WARN( _( "Faction '%s': %s" ), faction->name, lua_tostring( naevL, -1 ) );
      lua_pop( naevL, 1 );
      return _( "???" );
   }

   /* Parse return. */
   if ( !lua_isstring( naevL, -1 ) ) {
      WARN( _( "Lua script for faction '%s' did not return a %s from '%s'." ),
            faction->name, _( "string" ), "text_broad" );
      r = _( "???" );
   } else
      r = lua_tostring( naevL, -1 );
   lua_pop( naevL, 1 );
   return r;
}

/**
 * @brief Gets the maximum reputation of a faction.
 *
 *    @param f Faction to get maximum reputation of.
 *    @return Maximum value of the reputation with a faction.
 */
double faction_reputationMax( int f )
{
   Faction *faction;
   double   r;

   /* Ignore it if player is dead. */
   if ( player.p == NULL )
      return 0.;

   /* Escorts always have the same standing. */
   if ( f == FACTION_PLAYER )
      return 100.;

   faction = &faction_stack[f];

   if ( faction->lua_env == NULL )
      return 0.;

   /* Set up the method:
    * standing:reputation_max( standing ) */
   lua_rawgeti( naevL, LUA_REGISTRYINDEX, faction->lua_reputation_max );

   /* Call function. */
   if ( nlua_pcall( faction->lua_env, 0, 1 ) ) {
      /* An error occurred. */
      WARN( _( "Faction '%s': %s" ), faction->name, lua_tostring( naevL, -1 ) );
      lua_pop( naevL, 1 );
      return 0.;
   }

   /* Parse return. */
   if ( !lua_isnumber( naevL, -1 ) ) {
      WARN( _( "Lua script for faction '%s' did not return a %s from '%s'." ),
            faction->name, _( "number" ), "reputation_max" );
      r = 0.;
   } else
      r = lua_tonumber( naevL, -1 );
   lua_pop( naevL, 1 );
   return r;
}

/**
 * @brief Checks whether two factions are true neutral.
 *
 * The player isn't true neutral with anyone.
 *
 *    @param a Faction A.
 *    @param b Faction B.
 *    @return 1 if A and B are true neutral, 0 otherwise.
 */
int areNeutral( int a, int b )
{
   /* luckily our factions aren't masochistic */
   if ( a == b )
      return 0;

   /* Make sure they're valid. */
   if ( !faction_isFaction( a ) || !faction_isFaction( b ) )
      return 0;

   /* player handled separately */
   if ( a == FACTION_PLAYER )
      return 0;
   else if ( b == FACTION_PLAYER )
      return 0;

   return faction_grid[a * faction_mgrid + b] == GRID_NEUTRAL;
}

/**
 * @brief Checks whether two factions are enemies.
 *
 *    @param a Faction A.
 *    @param b Faction B.
 *    @return 1 if A and B are enemies, 0 otherwise.
 */
int areEnemies( int a, int b )
{
   /* luckily our factions aren't masochistic */
   if ( a == b )
      return 0;

   /* Make sure they're valid. */
   if ( !faction_isFaction( a ) || !faction_isFaction( b ) )
      return 0;

   /* player handled separately */
   if ( a == FACTION_PLAYER )
      return faction_isPlayerEnemy( b );
   else if ( b == FACTION_PLAYER )
      return faction_isPlayerEnemy( a );

   return faction_grid[a * faction_mgrid + b] == GRID_ENEMIES;
}

/**
 * @brief Checks whether two factions are allies or not.
 *
 *    @param a Faction A.
 *    @param b Faction B.
 *    @return 1 if A and B are allies, 0 otherwise.
 */
int areAllies( int a, int b )
{
   /* If they are the same they must be allies. */
   if ( a == b )
      return 1;

   /* Make sure they're valid. */
   if ( !faction_isFaction( a ) || !faction_isFaction( b ) )
      return 0;

   /* we assume player becomes allies with high rating */
   if ( a == FACTION_PLAYER )
      return faction_isPlayerFriend( b );
   else if ( b == FACTION_PLAYER )
      return faction_isPlayerFriend( a );

   return faction_grid[a * faction_mgrid + b] == GRID_ALLIES;
}

int areEnemiesSystem( int a, int b, const StarSystem *sys )
{
   /* luckily our factions aren't masochistic */
   if ( a == b )
      return 0;

   /* Make sure they're valid. */
   if ( !faction_isFaction( a ) || !faction_isFaction( b ) )
      return 0;

   /* player handled separately */
   if ( a == FACTION_PLAYER )
      return faction_isPlayerEnemySystem( b, sys );
   else if ( b == FACTION_PLAYER )
      return faction_isPlayerEnemySystem( a, sys );

   return faction_grid[a * faction_mgrid + b] == GRID_ENEMIES;
}

int areAlliesSystem( int a, int b, const StarSystem *sys )
{
   /* If they are the same they must be allies. */
   if ( a == b )
      return 1;

   /* Make sure they're valid. */
   if ( !faction_isFaction( a ) || !faction_isFaction( b ) )
      return 0;

   /* we assume player becomes allies with high rating */
   if ( a == FACTION_PLAYER )
      return faction_isPlayerFriendSystem( b, sys );
   else if ( b == FACTION_PLAYER )
      return faction_isPlayerFriendSystem( a, sys );

   return faction_grid[a * faction_mgrid + b] == GRID_ALLIES;
}

/**
 * @brief Checks whether or not a faction is valid.
 *
 *    @param f Faction to check for validity.
 *    @return 1 if faction is valid, 0 otherwise.
 */
int faction_isFaction( int f )
{
   if ( ( f < 0 ) || ( f >= array_size( faction_stack ) ) )
      return 0;
   return 1;
}

/**
 * @brief Parses a single faction, but doesn't set the allies/enemies bit.
 *
 *    @param temp Faction to load data into.
 *    @param file File to parse.
 *    @return Faction created from parent node.
 */
static int faction_parse( Faction *temp, const char *file )
{
   xmlNodePtr node, parent;
   int        saw_player;

   xmlDocPtr doc = xml_parsePhysFS( file );
   if ( doc == NULL )
      return -1;

   parent = doc->xmlChildrenNode; /* first faction node */
   if ( parent == NULL ) {
      WARN( _( "Malformed '%s' file: does not contain elements" ), file );
      return -1;
   }

   /* Clear memory. */
   memset( temp, 0, sizeof( Faction ) );
   temp->local_th           = 10.;
   temp->equip_env          = NULL;
   temp->sched_env          = NULL;
   temp->lua_env            = NULL;
   temp->lua_hit            = LUA_NOREF;
   temp->lua_hit_test       = LUA_NOREF;
   temp->lua_text_rank      = LUA_NOREF;
   temp->lua_text_broad     = LUA_NOREF;
   temp->lua_reputation_max = LUA_NOREF;

   xmlr_attr_strd( parent, "name", temp->name );
   if ( temp->name == NULL )
      WARN( _( "Faction from file '%s' has no name!" ), file );

   saw_player = 0;
   node       = parent->xmlChildrenNode;
   do {
      /* Only care about nodes. */
      xml_onlyNodes( node );

      /* Can be 0 or negative, so we have to take that into account. */
      if ( xml_isNode( node, "player" ) ) {
         temp->player_def = xml_getFloat( node );
         saw_player       = 1;
         continue;
      }

      xmlr_strd( node, "longname", temp->longname );
      xmlr_strd( node, "display", temp->displayname );
      xmlr_strd( node, "mapname", temp->mapname );
      xmlr_strd( node, "description", temp->description );
      xmlr_strd( node, "ai", temp->ai );
      xmlr_float( node, "local_th", temp->local_th );
      xmlr_float( node, "lane_length_per_presence",
                  temp->lane_length_per_presence );
      xmlr_float( node, "lane_base_cost", temp->lane_base_cost );
      if ( xml_isNode( node, "colour" ) ) {
         const char *ctmp = xml_get( node );
         if ( ctmp != NULL ) {
            const glColour *c = col_fromName( xml_raw( node ) );
            if ( c != NULL )
               temp->colour = *c;
            else
               WARN( _( "Faction '%s' has invalid colour '%s'!" ), temp->name,
                     ctmp );
         }
         /* If no named colour is present, RGB attributes are used. */
         else {
            /* Initialize in case a colour channel is absent. */
            xmlr_attr_float( node, "r", temp->colour.r );
            xmlr_attr_float( node, "g", temp->colour.g );
            xmlr_attr_float( node, "b", temp->colour.b );
            temp->colour.a = 1.;
            col_gammaToLinear( &temp->colour );
         }
         continue;
      }

      if ( xml_isNode( node, "known" ) ) {
         faction_setFlag( temp, FACTION_KNOWN );
         continue;
      }

      if ( xml_isNode( node, "logo" ) ) {
         char buf[PATH_MAX];
         if ( temp->logo != NULL ) {
            WARN( _( "Faction '%s' has duplicate 'logo' tag." ), temp->name );
            continue;
         }
         snprintf( buf, sizeof( buf ), FACTION_LOGO_PATH "%s",
                   xml_get( node ) );
         temp->logo = gl_newImage( buf, 0 );
         continue;
      }

      if ( xml_isNode( node, "static" ) ) {
         faction_setFlag( temp, FACTION_STATIC );
         continue;
      }

      if ( xml_isNode( node, "invisible" ) ) {
         faction_setFlag( temp, FACTION_INVISIBLE );
         continue;
      }

      if ( xml_isNode( node, "useshiddenjumps" ) ) {
         faction_setFlag( temp, FACTION_USESHIDDENJUMPS );
         continue;
      }

      if ( xml_isNode( node, "tags" ) ) {
         xmlNodePtr cur = node->children;
         if ( temp->tags != NULL )
            WARN( _( "Faction '%s' has duplicate '%s' node!" ), temp->name,
                  "tags" );
         else
            temp->tags = array_create( char * );
         do {
            xml_onlyNodes( cur );
            if ( xml_isNode( cur, "tag" ) ) {
               const char *tmp = xml_get( cur );
               if ( tmp != NULL )
                  array_push_back( &temp->tags, strdup( tmp ) );
               continue;
            }
            WARN( _( "Faction '%s' has unknown node in tags '%s'." ),
                  temp->name, cur->name );
         } while ( xml_nextNode( cur ) );
         continue;
      }

#if DEBUGGING
      /* Avoid warnings. */
      if ( xml_isNode( node, "allies" ) || xml_isNode( node, "enemies" ) ||
           xml_isNode( node, "neutrals" ) || xml_isNode( node, "generator" ) ||
           xml_isNode( node, "standing" ) || xml_isNode( node, "spawn" ) ||
           xml_isNode( node, "equip" ) )
         continue;
      WARN( _( "Unknown node '%s' in faction '%s'" ), node->name, temp->name );
#endif /* DEBUGGING */

   } while ( xml_nextNode( node ) );

   if ( !saw_player )
      WARN( _( "Faction '%s' missing 'player' tag." ), temp->name );
   if ( faction_isKnown_( temp ) &&
        !faction_isFlag( temp, FACTION_INVISIBLE ) &&
        temp->description == NULL )
      WARN( _( "Faction '%s' is known but missing 'description' tag." ),
            temp->name );

   xmlFreeDoc( doc );

   return 0;
}

/**
 * @brief Sets up a standing script for a faction.
 *
 *    @param temp Faction to associate the script to.
 *    @param scriptname Name of the Lua script to use (e.g., `static`).
 */
static void faction_addStandingScript( Faction *temp, const char *scriptname )
{
   char   buf[PATH_MAX], *dat;
   size_t ndat;

   snprintf( buf, sizeof( buf ), FACTIONS_PATH "standing/%s.lua", scriptname );
   temp->lua_env = nlua_newEnv( temp->name );

   nlua_loadStandard( temp->lua_env );
   dat = ndata_read( buf, &ndat );
   if ( nlua_dobufenv( temp->lua_env, dat, ndat, buf ) != 0 ) {
      WARN( _( "Failed to run standing script: %s\n"
               "%s\n"
               "Most likely Lua file has improper syntax, please check" ),
            buf, lua_tostring( naevL, -1 ) );
      nlua_freeEnv( temp->lua_env );
      temp->lua_env = NULL;
      free( dat );
      lua_pop( naevL, 1 );
      return;
   }
   free( dat );

   /* Set up the references. */
   temp->lua_hit = nlua_refenvtype( temp->lua_env, "hit", LUA_TFUNCTION );
   temp->lua_hit_test =
      nlua_refenvtype( temp->lua_env, "hit_test", LUA_TFUNCTION );
   temp->lua_text_broad =
      nlua_refenvtype( temp->lua_env, "text_broad", LUA_TFUNCTION );
   temp->lua_text_rank =
      nlua_refenvtype( temp->lua_env, "text_rank", LUA_TFUNCTION );
   temp->lua_reputation_max =
      nlua_refenvtype( temp->lua_env, "reputation_max", LUA_TFUNCTION );

   nlua_getenv( naevL, temp->lua_env, "friendly_at" );
   temp->friendly_at = lua_tonumber( naevL, -1 );
   lua_pop( naevL, 1 );
}

/**
 * @brief Parses the social titbits of a faction: allies and enemies.
 *
 *    @param file File to parse.
 *    @return 0 on success.
 */
static int faction_parseSocial( const char *file )
{
   char      *name;
   xmlNodePtr node, parent;
   Faction   *base;

   xmlDocPtr doc = xml_parsePhysFS( file );
   if ( doc == NULL )
      return -1;

   parent = doc->xmlChildrenNode; /* first faction node */
   if ( parent == NULL ) {
      WARN( _( "Malformed '%s' file: does not contain elements" ), file );
      return -1;
   }

   /* Get name. */
   base = NULL;
   xmlr_attr_strd( parent, "name", name );
   if ( name != NULL )
      base = &faction_stack[faction_get( name )];
   free( name );
   name = NULL;

   assert( base != NULL );

   /* Create arrays, not much memory so it doesn't really matter. */
   base->allies   = array_create( int );
   base->enemies  = array_create( int );
   base->neutrals = array_create( int );

   /* Parse social stuff. */
   node = parent->xmlChildrenNode;
   do {
      xml_onlyNodes( node );

      if ( xml_isNode( node, "generator" ) ) {
         FactionGenerator *fg;
         if ( base->generators == NULL )
            base->generators = array_create( FactionGenerator );
         fg = &array_grow( &base->generators );
         xmlr_attr_float( node, "weight", fg->weight );
         fg->id = faction_get( xml_get( node ) );
         continue;
      }

      /* Load script paths. */
      xmlr_strd( node, "standing", base->script_standing );
      xmlr_strd( node, "spawn", base->script_spawn );
      xmlr_strd( node, "equip", base->script_equip );

      /* Grab the allies */
      if ( xml_isNode( node, "allies" ) ) {
         xmlNodePtr cur = node->xmlChildrenNode;
         do {
            xml_onlyNodes( cur );
            if ( xml_isNode( cur, "ally" ) ) {
               int fct = faction_get( xml_get( cur ) );
               if ( faction_isFaction( fct ) )
                  array_push_back( &base->allies, fct );
            }
         } while ( xml_nextNode( cur ) );
         continue;
      }

      /* Grab the enemies. */
      if ( xml_isNode( node, "enemies" ) ) {
         xmlNodePtr cur = node->xmlChildrenNode;
         do {
            xml_onlyNodes( cur );
            if ( xml_isNode( cur, "enemy" ) ) {
               int fct = faction_get( xml_get( cur ) );
               if ( faction_isFaction( fct ) )
                  array_push_back( &base->enemies, fct );
            }
         } while ( xml_nextNode( cur ) );
         continue;
      }

      /* Grab the true neutral. */
      if ( xml_isNode( node, "neutrals" ) ) {
         xmlNodePtr cur = node->xmlChildrenNode;
         do {
            xml_onlyNodes( cur );
            if ( xml_isNode( cur, "neutral" ) ) {
               int fct = faction_get( xml_get( cur ) );
               if ( faction_isFaction( fct ) )
                  array_push_back( &base->neutrals, fct );
            }
         } while ( xml_nextNode( cur ) );
         continue;
      }
   } while ( xml_nextNode( node ) );

   xmlFreeDoc( doc );
   return 0;
}

/**
 * @brief Resets player standing and flags of factions to default.
 */
void factions_reset( void )
{
   factions_clearDynamic();

   /* Reset global standing. */
   for ( int i = 0; i < array_size( faction_stack ); i++ ) {
      faction_stack[i].player = faction_stack[i].player_def;
      faction_stack[i].flags  = faction_stack[i].oflags;
   }
   factions_resetLocal();
}

/**
 * @brief Reset local standing.
 */
void factions_resetLocal( void )
{
   StarSystem *sys_stack = system_getAll();
   for ( int i = 0; i < array_size( sys_stack ); i++ ) {
      StarSystem *sys = &sys_stack[i];
      for ( int j = 0; j < array_size( sys->presence ); j++ ) {
         SystemPresence *sp = &sys->presence[j];
         sp->local          = faction_reputation( sp->faction );
      }
   }
   // faction_updateGlobal();
}

void faction_updateSingle( int f )
{
   int         n         = 0;
   double      v         = 0.;
   StarSystem *sys_stack = system_getAll();
   for ( int j = 0; j < array_size( sys_stack ); j++ ) {
      StarSystem *sys = &sys_stack[j];
      for ( int k = 0; k < array_size( sys->presence ); k++ ) {
         SystemPresence *sp = &sys->presence[k];
         if ( sp->faction != f )
            continue;
         v += sp->local;
         n++;
      }
   }
   if ( n > 0 )
      faction_stack[f].player = v / (double)n;
}

/**
 * @brief Computes the global faction standing for each of the factions.
 */
void faction_updateGlobal( void )
{
   for ( int i = 0; i < array_size( faction_stack ); i++ )
      faction_updateSingle( i );
}

void faction_applyLocalThreshold( int f, StarSystem *sys )
{
   SystemPresence *srep =
      system_getFactionPresence( sys, f ); /* Starting reputation. */
   if ( srep == NULL )
      return;

#if DEBUGGING
   if ( srep->value <= 0. ) {
      WARN( "Trying to apply local faction threshold from system with no "
            "presence." );
      return;
   }
#endif /* DEBUGGING */

   Faction    *fct       = &faction_stack[f];
   double      n         = 0;
   double      rep       = srep->local;
   StarSystem *sys_stack = system_getAll();
   double      th        = fct->local_th;
   int         usehidden = faction_isFlag( fct, FACTION_USESHIDDENJUMPS );
   /* TODO avoid a memory allocation every call. */
   int *done   = array_create( int );
   int *queuea = array_create( int );
   int *queueb = array_create( int );

   array_push_back( &queuea, sys->id );
   array_push_back( &done, sys->id );
   while ( array_size( queuea ) > 0 ) {
      /* Go backwards through queue. */
      for ( int i = 0; i < array_size( queuea ); i++ ) {
         StarSystem *qsys = &sys_stack[queuea[i]];

         /* Update local presence. */
         srep = system_getFactionPresence( qsys, f );
         if ( srep != NULL )
            srep->local = CLAMP( MAX( rep - n * th, -100 ),
                                 MIN( rep + n * th, 100 ), srep->local );

         /* Propagate to next systems. */
         for ( int j = 0; j < array_size( qsys->jumps ); j++ ) {
            StarSystem *nsys  = qsys->jumps[j].target;
            int         found = 0;

            /* Don't propagate through hidden jumps if the faction doesn't use
             * hidden. */
            if ( jp_isFlag( &qsys->jumps[j], JP_HIDDEN ) && !usehidden )
               continue;

            /* Ignore systems already looked at. */
            for ( int k = 0; k < array_size( done ); k++ ) {
               if ( nsys->id == done[k] ) {
                  found = 1;
                  break;
               }
            }
            if ( found )
               continue;
            /* We only process systems that have presence. No presence systems
             * will act as a buffer. */
            if ( system_getPresence( nsys, f ) > 0. )
               array_push_back( &queueb, nsys->id );
            array_push_back( &done, nsys->id );
         }
      }
      array_erase( &queuea, array_begin( queuea ), array_end( queuea ) );

      /* Increment distance. */
      n++;

      /* Flip buffers. */
      int *queue = queuea;
      queuea     = queueb;
      queueb     = queue;
   }

   array_free( done );
   array_free( queuea );
   array_free( queueb );

   /* Update global standing. */
   faction_updateSingle( f );
}

/**
 * @brief Loads up all the factions from the data file.
 *
 *    @return 0 on success.
 */
int factions_load( void )
{
#if DEBUGGING
   Uint32 time = SDL_GetTicks();
#endif /* DEBUGGING */
   Faction *f;
   char   **faction_files = ndata_listRecursive( FACTION_DATA_PATH );

   /* player faction is hard-coded */
   faction_stack = array_create( Faction );
   f             = &array_grow( &faction_stack );
   memset( f, 0, sizeof( Faction ) );
   f->name               = strdup( "Player" );
   f->flags              = FACTION_STATIC | FACTION_INVISIBLE;
   f->equip_env          = NULL;
   f->sched_env          = NULL;
   f->lua_env            = NULL;
   f->lua_hit            = LUA_NOREF;
   f->lua_hit_test       = LUA_NOREF;
   f->lua_text_rank      = LUA_NOREF;
   f->lua_text_broad     = LUA_NOREF;
   f->lua_reputation_max = LUA_NOREF;
   f->allies             = array_create( int );
   f->enemies            = array_create( int );

   /* Add the base factions. */
   for ( int i = 0; i < array_size( faction_files ); i++ ) {
      if ( ndata_matchExt( faction_files[i], "xml" ) ) {
         Faction nf;
         int     ret = faction_parse( &nf, faction_files[i] );
         if ( ret == 0 ) {
            nf.oflags = nf.flags;
            array_push_back( &faction_stack, nf );
         }

         /* Render if necessary. */
         naev_renderLoadscreen();
      }
   }

   /* Sort by name. */
   qsort( faction_stack, array_size( faction_stack ), sizeof( Faction ),
          faction_cmp );
   faction_player = faction_get( "Player" );

   /* Second pass - sets allies and enemies */
   for ( int i = 0; i < array_size( faction_files ); i++ ) {
      if ( ndata_matchExt( faction_files[i], "xml" ) ) {
         faction_parseSocial( faction_files[i] );
      }
   }

   /* Third pass, Make allies/enemies symmetric. */
   for ( int i = 0; i < array_size( faction_stack ); i++ ) {
      f = &faction_stack[i];

      /* First run over allies and make sure it's mutual. */
      for ( int j = 0; j < array_size( f->allies ); j++ ) {
         const Faction *sf = &faction_stack[f->allies[j]];
         int            r  = 0;
         for ( int k = 0; k < array_size( sf->allies ); k++ )
            if ( sf->allies[k] == i ) {
               r = 1;
               break;
            }

         /* Add ally if necessary. */
         if ( r == 0 )
            faction_addAlly( f->allies[j], i );
      }

      /* Now run over enemies. */
      for ( int j = 0; j < array_size( f->enemies ); j++ ) {
         const Faction *sf = &faction_stack[f->enemies[j]];
         int            r  = 0;
         for ( int k = 0; k < array_size( sf->enemies ); k++ )
            if ( sf->enemies[k] == i ) {
               r = 1;
               break;
            }

         if ( r == 0 )
            faction_addEnemy( f->enemies[j], i );
      }
   }

   /* Clean up stuff. */
   for ( int i = 0; i < array_size( faction_files ); i++ )
      free( faction_files[i] );
   array_free( faction_files );

   /* Compute grid and finalize. */
   faction_computeGrid();
#if DEBUGGING
   if ( conf.devmode ) {
      time = SDL_GetTicks() - time;
      DEBUG( n_( "Loaded %d Faction in %.3f s", "Loaded %d Factions in %.3f s",
                 array_size( faction_stack ) ),
             array_size( faction_stack ), time / 1000. );
   } else
      DEBUG( n_( "Loaded %d Faction", "Loaded %d Factions",
                 array_size( faction_stack ) ),
             array_size( faction_stack ) );
#endif /* DEBUGGING */

   return 0;
}

int factions_loadPost( void )
{
   for ( int i = 0; i < array_size( faction_stack ); i++ ) {
      Faction *f = &faction_stack[i];

      /* Standing scripts. */
      if ( f->script_standing != NULL )
         faction_addStandingScript( f, f->script_standing );

      if ( f->script_spawn != NULL ) {
         char   buf[PATH_MAX], *dat;
         size_t ndat;

         snprintf( buf, sizeof( buf ), FACTIONS_PATH "spawn/%s.lua",
                   f->script_spawn );
         f->sched_env = nlua_newEnv( buf );
         nlua_loadStandard( f->sched_env );
         dat = ndata_read( buf, &ndat );
         if ( nlua_dobufenv( f->sched_env, dat, ndat, buf ) != 0 ) {
            WARN( _( "Failed to run spawn script: %s\n"
                     "%s\n"
                     "Most likely Lua file has improper syntax, please check" ),
                  buf, lua_tostring( naevL, -1 ) );
            nlua_freeEnv( f->sched_env );
            f->sched_env = NULL;
         }
         free( dat );
      }

      if ( f->script_equip != NULL ) {
         char   buf[PATH_MAX], *dat;
         size_t ndat;

         snprintf( buf, sizeof( buf ), FACTIONS_PATH "equip/%s.lua",
                   f->script_equip );
         f->equip_env = nlua_newEnv( buf );
         nlua_loadStandard( f->equip_env );
         dat = ndata_read( buf, &ndat );
         if ( nlua_dobufenv( f->equip_env, dat, ndat, buf ) != 0 ) {
            WARN( _( "Failed to run equip script: %s\n"
                     "%s\n"
                     "Most likely Lua file has improper syntax, please check" ),
                  buf, lua_tostring( naevL, -1 ) );
            nlua_freeEnv( f->equip_env );
            f->equip_env = NULL;
         }
         free( dat );
      }

      /* Make sure functions that need Lua have it. */
      if ( ( f->lua_env == NULL ) && !faction_isFlag( f, FACTION_STATIC ) )
         WARN( _( "Faction '%s' has no Lua and isn't static!" ), f->name );
   }
   return 0;
}

/**
 * @brief Frees a single faction.
 */
static void faction_freeOne( Faction *f )
{
   free( f->name );
   free( f->longname );
   free( f->displayname );
   free( f->mapname );
   free( f->description );
   free( f->ai );
   free( f->script_standing );
   free( f->script_spawn );
   free( f->script_equip );
   array_free( f->generators );
   gl_freeTexture( f->logo );
   array_free( f->allies );
   array_free( f->enemies );
   array_free( f->neutrals );
   nlua_freeEnv( f->sched_env );
   nlua_freeEnv( f->lua_env );
   if ( !faction_isFlag( f, FACTION_DYNAMIC ) )
      nlua_freeEnv( f->equip_env );
   for ( int i = 0; i < array_size( f->tags ); i++ )
      free( f->tags[i] );
   array_free( f->tags );
}

/**
 * @brief Frees the factions.
 */
void factions_free( void )
{
   /* Free factions. */
   for ( int i = 0; i < array_size( faction_stack ); i++ )
      faction_freeOne( &faction_stack[i] );
   array_free( faction_stack );
   faction_stack = NULL;

   /* Clean up faction grid. */
   free( faction_grid );
   faction_grid  = NULL;
   faction_mgrid = 0;
}

/**
 * @brief Saves player's standings with the factions.
 *
 *    @param writer The xml writer to use.
 *    @return 0 on success.
 */
int pfaction_save( xmlTextWriterPtr writer )
{
   xmlw_startElem( writer, "factions" );

   for ( int i = 0; i < array_size( faction_stack ); i++ ) {
      const Faction *f = &faction_stack[i];

      /* Must not be static. */
      if ( faction_isFlag( f, FACTION_STATIC ) )
         continue;

      xmlw_startElem( writer, "faction" );

      xmlw_attr( writer, "name", "%s", f->name );
      xmlw_elem( writer, "standing", "%f", f->player );
      if ( faction_isFlag( f, FACTION_REPOVERRIDE ) )
         xmlw_elem( writer, "override", "%f", f->override );

      if ( faction_isKnown_( f ) )
         xmlw_elemEmpty( writer, "known" );

      xmlw_endElem( writer ); /* "faction" */
   }

   xmlw_endElem( writer ); /* "factions" */
   return 0;
}

/**
 * @brief Loads the player's faction standings.
 *
 *    @param parent Parent xml node to read from.
 *    @return 0 on success.
 */
int pfaction_load( xmlNodePtr parent )
{
   xmlNodePtr node = parent->xmlChildrenNode;

   do {
      if ( xml_isNode( node, "factions" ) ) {
         xmlNodePtr cur = node->xmlChildrenNode;
         do {
            if ( xml_isNode( cur, "faction" ) ) {
               int   faction;
               char *str;
               xmlr_attr_strd( cur, "name", str );
               faction = faction_get( str );
               free( str );
               if ( faction < 0 )
                  continue;

               xmlNodePtr sub = cur->xmlChildrenNode;
               Faction   *fct = &faction_stack[faction];
               do {
                  if ( xml_isNode( sub, "standing" ) ) {

                     /* Must not be static. */
                     if ( !faction_isFlag( fct, FACTION_STATIC ) )
                        fct->player = xml_getFloat( sub );
                     continue;
                  }
                  if ( xml_isNode( sub, "known" ) ) {
                     faction_setFlag( fct, FACTION_KNOWN );
                     continue;
                  }
                  if ( xml_isNode( sub, "override" ) ) {
                     fct->override = xml_getFloat( sub );
                     faction_setFlag( fct, FACTION_REPOVERRIDE );
                     continue;
                  }
               } while ( xml_nextNode( sub ) );
            }
         } while ( xml_nextNode( cur ) );
      }
   } while ( xml_nextNode( node ) );

   return 0;
}

/**
 * @brief Returns an array of faction ids.
 *
 *    @param which Which factions to get. (0,1,2,3 : all, friendly, neutral,
 * hostile)
 *    @return Array (array.h): The faction IDs of the specified alignment.
 */
int *faction_getGroup( int which )
{
   int *group;

   switch ( which ) {
   case 0: /* 'all' */
      return array_copy( int, faction_stack );

   case 1: /* 'friendly' */
      group = array_create( int );
      for ( int i = 0; i < array_size( faction_stack ); i++ )
         if ( areAllies( FACTION_PLAYER, i ) )
            array_push_back( &group, i );
      return group;

   case 2: /* 'neutral' */
      group = array_create( int );
      for ( int i = 0; i < array_size( faction_stack ); i++ )
         if ( !areAllies( FACTION_PLAYER, i ) &&
              !areEnemies( FACTION_PLAYER, i ) )
            array_push_back( &group, i );
      return group;

   case 3: /* 'hostile' */
      group = array_create( int );
      for ( int i = 0; i < array_size( faction_stack ); i++ )
         if ( areEnemies( FACTION_PLAYER, i ) )
            array_push_back( &group, i );
      return group;

   default:
      return NULL;
   }
}

double faction_reputationOverride( int f, int *set )
{
   if ( !faction_isFaction( f ) ) {
      *set = -1;
      return 0.;
   }
   Faction *fct = &faction_stack[f];
   *set         = faction_isFlag( fct, FACTION_REPOVERRIDE );
   return fct->override;
}

void faction_setReputationOverride( int f, int set, double value )
{
   if ( !faction_isFaction( f ) )
      return;
   Faction *fct = &faction_stack[f];
   if ( !set ) {
      faction_rmFlag( fct, FACTION_REPOVERRIDE );
      return;
   }
   faction_setFlag( fct, FACTION_REPOVERRIDE );
   fct->override = value;
}

/**
 * @brief Checks to see if a faction uses hidden jumps.
 */
int faction_usesHiddenJumps( int f )
{
   if ( faction_isFaction( f ) )
      return faction_isFlag( &faction_stack[f], FACTION_USESHIDDENJUMPS );
   return 0;
}

/**
 * @brief Gets the faction's generators.
 */
const FactionGenerator *faction_generators( int f )
{
   if ( faction_isFaction( f ) )
      return faction_stack[f].generators;
   return NULL;
}

/**
 * @brief Clears dynamic factions.
 */
void factions_clearDynamic( void )
{
   for ( int i = array_size( faction_stack ) - 1; i >= 0; i-- ) {
      Faction *f = &faction_stack[i];
      if ( !faction_isFlag( f, FACTION_DYNAMIC ) )
         continue;
      faction_freeOne( f );
      array_erase( &faction_stack, f, f + 1 );
   }
   faction_computeGrid();
}

/**
 * @brief Dynamically add a faction.
 *
 *    @param base Faction to base it off (negative for none).
 *    @param name Name of the faction to set.
 *    @param display Display name to use.
 *    @param ai Default pilot AI to use (if NULL, inherit from base).
 *    @param colour Default colour to use (if NULL, inherit from base).
 */
int faction_dynAdd( int base, const char *name, const char *display,
                    const char *ai, const glColour *colour )
{
   Faction *f = &array_grow( &faction_stack );
   memset( f, 0, sizeof( Faction ) );
   f->name        = strdup( name );
   f->displayname = ( display == NULL ) ? NULL : strdup( display );
   f->ai          = ( ai == NULL ) ? NULL : strdup( ai );
   f->allies      = array_create( int );
   f->enemies     = array_create( int );
   f->equip_env   = NULL;
   f->lua_env     = NULL;
   f->sched_env   = NULL;
   f->flags =
      FACTION_STATIC | FACTION_INVISIBLE | FACTION_DYNAMIC | FACTION_KNOWN;
   faction_addStandingScript( f, "static" );
   if ( base >= 0 ) {
      Faction *bf = &faction_stack[base];

      if ( bf->ai != NULL && f->ai == NULL )
         f->ai = strdup( bf->ai );
      if ( bf->logo != NULL )
         f->logo = gl_dupTexture( bf->logo );

      for ( int i = 0; i < array_size( bf->allies ); i++ ) {
         int *tmp = &array_grow( &f->allies );
         *tmp     = bf->allies[i];
      }
      for ( int i = 0; i < array_size( bf->enemies ); i++ ) {
         int *tmp = &array_grow( &f->enemies );
         *tmp     = bf->enemies[i];
      }

      f->player_def = bf->player_def;
      f->player     = bf->player;
      f->colour     = bf->colour;

      /* Lua stuff. */
      f->equip_env = bf->equip_env;
   }

   /* Copy colour over if applicable. */
   if ( colour != NULL )
      f->colour = *colour;

   /* TODO make this incremental. */
   faction_computeGrid();

   return f - faction_stack;
}

/**
 * @brief Computes the faction relationship grid.
 */
static void faction_computeGrid( void )
{
   size_t n = array_size( faction_stack );
   if ( faction_mgrid < n ) {
      free( faction_grid );
      faction_grid  = malloc( n * n * sizeof( int ) );
      faction_mgrid = n;
   }
   n = faction_mgrid;
   memset( faction_grid, 0, n * n * sizeof( int ) );
   for ( int i = 0; i < array_size( faction_stack ); i++ ) {
      Faction *fa = &faction_stack[i];
      for ( int k = 0; k < array_size( fa->allies ); k++ ) {
         int j = fa->allies[k];
#if DEBUGGING
         int fij = faction_grid[i * n + j];
         int fji = faction_grid[j * n + i];
         if ( ( fij != GRID_ALLIES && fij != GRID_NONE ) ||
              ( fji != GRID_ALLIES && fji != GRID_NONE ) )
            WARN( "Incoherent faction grid! '%s' and '%s' already have a "
                  "relationship, "
                  "but trying to set to allies!",
                  faction_stack[i].name, faction_stack[j].name );
#endif /* DEBUGGING */
         faction_grid[i * n + j] = GRID_ALLIES;
         faction_grid[j * n + i] = GRID_ALLIES;
      }
      for ( int k = 0; k < array_size( fa->enemies ); k++ ) {
         int j = fa->enemies[k];
#if DEBUGGING
         int fij = faction_grid[i * n + j];
         int fji = faction_grid[j * n + i];
         if ( ( fij != GRID_ENEMIES && fij != GRID_NONE ) ||
              ( fji != GRID_ENEMIES && fji != GRID_NONE ) )
            WARN( "Incoherent faction grid! '%s' and '%s' already have a "
                  "relationship, "
                  "but trying to set to enemies!",
                  faction_stack[i].name, faction_stack[j].name );
#endif /* DEBUGGING */
         faction_grid[i * n + j] = GRID_ENEMIES;
         faction_grid[j * n + i] = GRID_ENEMIES;
      }
      for ( int k = 0; k < array_size( fa->neutrals ); k++ ) {
         int j = fa->neutrals[k];
#if DEBUGGING
         int fij = faction_grid[i * n + j];
         int fji = faction_grid[j * n + i];
         if ( ( fij != GRID_NEUTRAL && fij != GRID_NONE ) ||
              ( fji != GRID_NEUTRAL && fji != GRID_NONE ) )
            WARN( "Incoherent faction grid! '%s' and '%s' already have a "
                  "relationship, "
                  "but trying to set to neutrals!",
                  faction_stack[i].name, faction_stack[j].name );
#endif /* DEBUGGING */
         faction_grid[i * n + j] = GRID_NEUTRAL;
         faction_grid[j * n + i] = GRID_NEUTRAL;
      }
   }
}
